import { isObject } from "../utils";
import { arrayMethods } from "./array";
import Dep from "./dep.js";

// 1. 如果数据是对象， 会将对象不停的递归， 进行劫持
// 2. 如果是数组，会劫持数组的方法， 并对数组中不是基本数据类型的数据进行检测

// 如果给对象新增一个属性不会触发视图更新，（给对象本身也增加一个dep， dep中存watcher， 如果增加一个属性后， 我就手动
// 的触发一个watcher的更新）
class Observer {
  constructor(data) {
    this.dep = new Dep();
    // 对对象汇总的所有属性， 进行劫持
    Object.defineProperty(data, "__ob__", {
      value: this,
      enumerable: false,
    }); // 所有被劫持过的属性都有__ob__
    // 数组劫持的逻辑
    if (Array.isArray(data)) {
      // 数组劫持的逻辑
      // 对数组原来的方法进行改写 --> 切片编程   高阶函数
      data.__proto__ = arrayMethods;
      // 如果数组中的数据是对象类型， 需要监控对象的变化
      this.observeArray(data);
    } else {
      this.walk(data); // 对象劫持的逻辑
    }
  }

  observeArray(data) {
    // 对我们数组的数组 和 数组中的对象再次劫持  递归了
    data.forEach((item) => {

      // 如果数组里放的是对象类型， 也做了观测， JSON.stringify() 也做了收集依赖了
      observe(item);
    });
  }

  walk(data) {
    // 这种方法 不会循环原型链上的东西
    Object.keys(data).forEach((key) => {
      defineReactive(data, key, data[key]);
    });
  }
}

function dependArray(value) {
  for (let i = 0; i < value.length; i++) {
    const current = value[i];
    current.__ob__ && current.__ob__.dep.depend();
    if (Array.isArray(current)) {
      dependArray(current)
    }
  }
}

// vue2 会对对象进行遍历， 将每个属性， 用defineProperty重新定义， 性能差
function defineReactive(data, key, value) {
  // 劫持的注意1： value有可能是对象
  let childObj = observe(value); // 本身用户默认值是对象套对象， 需要递归处理（性能更差）
  let dep = new Dep();// 每个属性都有一个dep属性

  // childObj
  // console.log(data, key, value);
  Object.defineProperty(data, key, {
    get() {
      // 取值时， watcher和dep对应起来
      if (Dep.target) {
        // 此值是在模板中取值的
        dep.depend(); // 让dep记住这个target
        if (childObj) { // 可能是数组， 也可能是对象， 对象也要收集依赖， 后续写$set方法是需要触发他自己的更新
          childObj.dep.depend(); // 就是让数组和对象也记录watcher
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value;
    },
    set(newValue) {
      // todo... 更新视图
      // 劫持的注意2： 用户赋值的时是一个对象，需要对这个对象进行又一次劫持
      if (newValue == value) {
        return;
      }
      observe(newValue);
      value = newValue;
      dep.notify();
    },
  });
  // 劫持的注意3： 新增的属性是不能够被劫持的
  //         实现方式： 通过Vue.$set或者直接赋值对象
}

export function observe(data) {
  // console.log(data);
  // 1. 判断数据： 如果是对象才观测
  if (!isObject(data)) {
    return;
  }
  // 默认最外层的data必须是一个对象

  // 如果观测过了， 就跳过观测了
  if (data.__ob__) {
    return data.__ob__;
  }
  return new Observer(data);
}
